# -*- coding: utf-8 -*-

import logging
import re


class CommandError(Exception):
    pass


class CancelPrintObject:

    def __init__(self, config):

        self.config = config
        self.printer = config.get_printer()
        self.printer.register_event_handler("klippy:connect", self._handle_connect)
        self.vsdcard = None
        self.gcode_move = None

        self.supported_slicer = ['cura', 'custom']
        self.slicer = config.get('slicer', 'cura').lower()
        self.pattern = config.get('pattern', '')

        if self.slicer in self.supported_slicer:
            self.match_temp = getattr(self, '_{}_temp'.format(self.slicer))
            self._run_script = getattr(self, '_{}_run_script'.format(self.slicer))
            self.log('using slicer:{}'.format(self.slicer))
        else:
            self.log('slicer is not supported, using custom pattern:[{}]'.format(self.pattern))
            self.match_temp = self._custom_temp

        gcode = self.printer.lookup_object('gcode')
        gcode.register_command('RESET_EXCLUDE_OBJECT', self.cmd_reset_exclude_object)
        gcode.register_command('PRINTED_OBJECTS', self.cmd_show_printed_objects)
        gcode.register_command('CURRENT_OBJECT', self.cmd_show_current_object)
        gcode.register_command('EXCLUDE_OBJECT', self.cmd_exclude_object)

        self.gcode = gcode
        self.origin_run_script = None
        self.printed_objects = set()
        self.cancelled_objects = set()
        self.last_object = None
        self.last_position = [0., 0., 0., 0.]

    def log(self, msg):
        logging.info('Cancel object:{}'.format(msg))

    def cmd_reset_exclude_object(self, *args):
        self.printed_objects = set()
        self.cancelled_objects = set()
        self.last_object = None
        self.last_position = [0., 0., 0., 0.]

    def _handle_connect(self):
        self.vsdcard = self.printer.lookup_object('virtual_sdcard')
        # 劫持 gcode 的 run_script 函数
        self.gcode_move = self.printer.lookup_object('gcode_move')
        self.origin_run_script = self.gcode.run_script
        self.gcode.run_script = self.run_script

    def _cura_temp(self, script):
        res = re.search(';MESH:(.*)', script)
        if not res:
            return ''
        obj_name = ''
        if len(res.regs) < 2:
            obj_name = res.group()
        else:
            obj_name = res.group(1)

        return obj_name.strip()

    def parse_move_params(self, script):
        try:
            parts = self.arg_split.split(script)
            params = {parts[i]: float(parts[i + 1].strip())
                      for i in range(1, len(parts), 2)}
        except:
            params = {}
        return params

    def _cura_run_script(self, script):
        if script.startswith('print_start'):
            self.cmd_reset_exclude_object(None)

        is_set_position = script.startswith('92')
        is_move = script.startswith('G1') or script.startswith('G0') or is_set_position

        params = []
        if is_move:
            params = self.parse_move_params(script)

        obj_name = None
        if script.startswith(';'):
            obj_name = self.match_temp(script)

        if obj_name and obj_name not in self.cancelled_objects and self.last_object in self.cancelled_objects:
            params = {}
            for i, axis in enumerate('XYZ'):
                if self.gcode_move.absolute_coord:
                    params[axis] = self.last_position[i]
                else:
                    params[axis] = self.last_position[i] - self.gcode_move.last_position[i]
            gcmd = self.gcode.create_gcode_command('G1', 'G1', params)
            self.gcode_move.cmd_G1(gcmd)

            delta_E = self.last_position[3] - self.gcode_move.last_position[3]
            self.gcode_move.base_position[3] -= delta_E

        if self.last_object not in self.cancelled_objects:
            self.origin_run_script(script)
            self.last_position = self.gcode_move.last_position[:]
        else:
            self.log('on cancelled objectes')
            if is_set_position:
                for i, axis in enumerate('XYZE'):
                    if axis not in params:
                        continue
                    if i == 3:
                        params[i] *= self.gcode_move.extrude_factor
                    self.gcode_move.base_position = self.last_position[i] - params[axis]
                if not params:
                    self.gcode_move.base_position = list(self.last_position)

            elif is_move:
                for i, axis in enumerate('XYZ'):
                    if axis in params:
                        v = params[axis]
                        if not self.gcode_move.absolute_coord:
                            self.last_position[i] += v
                        else:
                            # value relative to base coordinate position
                            self.last_position[i] = v + self.gcode_move.base_position[i]

                if 'E' in params:
                    v = params['E'] * self.gcode_move.extrude_factor
                    if not self.gcode_move.absolute_coord or not self.gcode_move.absolute_extrude:
                        self.last_position[3] += v
                    else:
                        self.last_position[3] = v + self.gcode_move.base_position[3]

        if obj_name:
            self.printed_objects.add(obj_name)
            self.last_object = obj_name

    arg_split = re.compile('([A-Z_]+|[A-Z*/])')

    def cmd_show_printed_objects(self, gcmd):
        if self.slicer == 'cura':
            gcmd.respond_info('打印零件列表:\n' + '\n'.join([file for file in list(self.printed_objects) if file != 'NONMESH']))
            return
        gcmd.respond_info('打印零件列表:\n' + '\n'.join(list(self.printed_objects)))

    def cmd_show_current_object(self, gcmd):
        gcmd.respond_info('正在打印的模型:「{}」'.format(self.last_object))

    def cmd_exclude_object(self, gcmd):
        params = gcmd.get_command_parameters()
        if 'NAME' in params:
            obj_name = params['NAME'].strip()
            if obj_name in self.printed_objects:
                self.cancelled_objects.add(obj_name)
                self.log('add cencelled object :[{}]'.format(obj_name))
                gcmd.respond_info('取消打印零件：[{}]'.format(obj_name))
            else:
                gcmd.respond_info('零件：[{}] 不存在'.format(obj_name))
        else:
            gcmd.respond_info('已经取消打印的模型:\n' + '\n'.join(list(self.cancelled_objects)))

    extrusion_template = re.compile('E(-?\d*\.?\d*)')

    def run_script(self, script):
        # 来自 sd 卡以外的gcode放行，否则拦截
        if not self.vsdcard.cmd_from_sd:
            self.origin_run_script(script)
        else:
            self._run_script(script)

    def match_temp(self, *args, **kwargs):
        pass

    def _run_script(self, *args, **kwargs):
        pass


def load_config(config):
    return CancelPrintObject(config)
